﻿using System;
using System.Linq;
using System.Reflection;

namespace MLSharp.Utilities
{
	/// <summary>
	/// Helper that wraps up some reflection-related functionality 
	/// into easier-to-use methods.
	/// </summary>
	public static class Reflector
	{
		/// <summary>
		/// Sets the target's property to the specified value.
		/// </summary>
		/// <param name="target"></param>
		/// <param name="property"></param>
		/// <param name="value"></param>
		public static void SetPropertyValue(object target, PropertyInfo property, string value)
		{
			object realValue;

			if (property.PropertyType == typeof (bool))
			{
				realValue = bool.Parse(value);
			}
			else if (property.PropertyType == typeof (int))
			{
				realValue = int.Parse(value);
			}
			else if (property.PropertyType == typeof (double))
			{
				realValue = double.Parse(value);
			}
			else if (property.PropertyType.IsEnum)
			{
				realValue = Enum.Parse(property.PropertyType, value);
			}
			else if (property.PropertyType == typeof (string))
			{
				realValue = value;
			}
			else if (property.PropertyType.IsArray && property.PropertyType.GetElementType() == typeof(int))
			{
				realValue = value.Split(',').Select(v => int.Parse(v)).ToArray();
			}
			else if (property.PropertyType.IsArray && property.PropertyType.GetElementType() == typeof(string))
			{
				realValue = value.Split(',').Select(v => v.ToString()).ToArray();
			}
			else
			{
				string error = string.Format("Type {0} is not supported, can't set {1} on object of type {2}.",
				                             property.PropertyType.Name, property.Name, target.GetType());

				throw new NotSupportedException(error);
			}

			try
			{
				property.SetValue(target, realValue, null);
			}
			catch (Exception ex)
			{
				throw new ReflectorException(
					"Unable to set property " + property.Name + " to value " + value + " on object of type " + target.GetType().Name +
					".", ex);
			}
		}

		/// <summary>
		/// Creates an instance of the specified type.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="type"></param>
		/// <returns></returns>
		public static T CreateInstance<T>(Type type)
		{
			T result;

			try
			{
				result = (T) Activator.CreateInstance(type);
			}
			catch(Exception ex)
			{
				throw new TypeLoadException("Unable to load " + type, ex);
			}

			return result;
		}

		/// <summary>
		/// Sets the specified property to the specified value.
		/// </summary>
		/// <param name="target"></param>
		/// <param name="propertyName"></param>
		/// <param name="value"></param>
		public static void SetPropertyValue(object target, string propertyName, string value)
		{
			SetPropertyValue(target, GetWritableProperty(target, propertyName), value);
		}

		/// <summary>
		/// Gets the specified writable property.
		/// </summary>
		/// <param name="target"></param>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		private static PropertyInfo GetWritableProperty(object target, string propertyName)
		{
			PropertyInfo property = target.GetType().GetProperty(propertyName);

			if (property == null)
			{
				string error = string.Format("Unable to find a property named '{0}' on object of type '{1}'.",
				                             propertyName, target.GetType());

				throw new ReflectorException(error);
			}

			if (!property.CanWrite)
			{
				throw new InvalidOperationException(propertyName + " is not writeable.");
			}

			return property;
		}
	}
}
